<script lang="ts" setup>
  import sparkMD5 from 'spark-md5'
  import axios from 'axios'

  let file:null | File = null
  const CHUNK_SIZE = 10 * 1024 * 1024 
  const fileChange = (e: any) => {
    e.target.file
  }

  // 创建分片
  const createFileChunk = (file: File,size=CHUNK_SIZE) => {
    const chunks = [] 
    let cur = 0
    while(cur<file.size){
      chunks.push({index:cur, file:file.slice(cur,cur+size)})
      cur+=size
    }
    return chunks
  }

  // 文件哈希
  const calculateHashSample = (file: File): Promise<string> => {

      // 布隆过滤器  判断一个数据存在与否
      // 1个G的文件，抽样后5M以内
      // hash一样，文件不一定一样
      // hash不一样，文件一定不一样
      return new Promise(resolve=>{
        const spark = new sparkMD5.ArrayBuffer()
        const reader = new FileReader()
        const size = file!.size
        const offset = 2*1024*1024
        // 第一个2M，最后一个区块数据全要
        let chunks = [file.slice(0,offset)]

        let cur = offset
        while(cur<size){
          if(cur+offset>=size){
            // 最后一个区快
            chunks.push(file.slice(cur, cur+offset))

          }else{
            // 中间的区块
            const mid = cur+offset/2
            const end = cur+offset
            chunks.push(file.slice(cur, cur+2))
            chunks.push(file.slice(mid, mid+2))
            chunks.push(file.slice(end-2, end))
          }
          cur+=offset
        }
        // 中间的，取前中后各2各字节
        reader.readAsArrayBuffer(new Blob(chunks))
        reader.onload = e=>{
          spark.append(e.target!.result as ArrayBuffer)
          resolve(spark.end())
        }
      })
  }
  const uploadFile = async () => {
    if(!file){ return }
    
    // 1. 大文件拆小
    const chunks = createFileChunk(file)

    // 2. 根据当前的文件，得到一个唯一标识
    const hash = await calculateHashSample(file)

    // 3. 预上传
    // 问一下后端，文件是否上传过，如果没有，是否有存在的切片
    const {data:{uploaded, uploadedList}} = await axios.post('/api/checkfile',{
      hash,
      ext:file.name.split('.').pop()
    })
    if(uploaded){
      // 秒传
      return alert('秒传')
    }

    // 4. 上传全部片段
    await uploadChunks(uploadedList, chunks, hash)

    // 5. 通知服务器合并片段
    await mergeRequest(hash)
  }
  const uploadChunks = async (uploadedList:string[]=[], chunks: {
      index: number;
      file: Blob;
    }[], hash: string) => {
    const _chunks = chunks.map((chunk,index)=>{
        // 切片的名字 hash+index
        const name = hash +'-'+ index
        return {
          hash,
          name,
          index,
          chunk:chunk.file,
          // 设置进度条，已经上传的，设为100
          progress: uploadedList.indexOf(name)>-1 ?100:0
        }
      })

      const requests = _chunks
        .filter(chunk=>uploadedList.indexOf(chunk.name)==-1) // 过滤之前已近上传的内容
        .map((chunk,index)=>{
          // 转成promise
          const form = new FormData()
          form.append('chunk',chunk.chunk)
          form.append('hash',chunk.hash)
          form.append('name',chunk.name)
          // form.append('index',chunk.index)
          return {form, index:chunk.index,error:0}
        })
        .map(({form,index})=> axios.post('/api/uploadfile',form,{
          onUploadProgress:progress=>{
            // 不是整体的进度条了，而是每个区块有自己的进度条，整体的进度条需要计算
            _chunks[index].progress = Number(((progress.loaded/progress.total)*100).toFixed(2))
          }
        }))


      await Promise.all(requests)
    }
    // TCP慢启动，先上传一个初始区块，比如10KB，根据上传成功时间，决定下一个区块仕20K，hi是50K，还是5K
    // 在下一个一样的逻辑，可能编程100K，200K，或者2K
    // 上传可能报错
    // 报错之后，进度条变红，开始重试
    // 一个切片重试失败三次，整体全部终止
    const sendRequest = async (chunks,limit=4) => {
      // limit仕并发数
      // 一个数组,长度仕limit
      // [task12,task13,task4]
      return new Promise((resolve,reject)=>{
        const len = chunks.length
        let counter = 0 
        let isStop = false
        const start = async ()=>{
          if(isStop){
            return 
          }
          const task = chunks.shift()
          if(task){
            const {form,index} = task

            try{
              await  axios.post('/uploadfile',form,{
                onUploadProgress:progress=>{
                  // 不是整体的进度条了，而是每个区块有自己的进度条，整体的进度条需要计算
                  chunks[index].progress = Number(((progress.loaded/progress.total)*100).toFixed(2))
                }
              })
              if(counter==len-1){
                // 最后一个任务
                resolve('')
              }else{
                counter++
                // 启动下一个任务
                start()
              }
            }catch(e){

              chunks[index].progress = -1
              if(task.error<3){
                task.error++
                chunks.unshift(task)
                start()
              }else{
                // 错误三次
                isStop = true
                reject()
              }
            }
          }
        }

        while(limit>0){
          // 启动limit个任务
          // 模拟一下延迟
          setTimeout(()=>{
            start()
          },Math.random()*2000)
          limit-=1
        }
      
      })
    }
    const mergeRequest = async (hash: string) => {
      if(!file) return
      const ret = await axios.post('/api/mergefile',{
        ext: file.name.split('.').pop(),
        size:CHUNK_SIZE,
        hash
      })
      const url = ret.data.url
      // await axios.put('/user/info',{url:"/api"+url})
    }

    const handleFileChange = (e: any) => {

      file = e.target.files[0]
      if(!file) return 
    }


</script>
<template>
<div>
  <input type="file"  @change="handleFileChange" />
  <button @click="uploadFile">上传</button>
</div>
</template>